using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
using System.Reflection;
using System.CodeDom.Compiler;
using System.Windows.Forms;

namespace MDXInfo.Util.Script
{
    public delegate void CompilerOutputDelegate(string outputLine);
    public delegate void ScriptSourceChangedHandler(string newSource, string oldSource);

    public class Script
    {
        public event ScriptSourceChangedHandler ScriptSourceChanged;

        /// <summary>
        /// This method contructs the default permission set our scripts may use.
        /// Host applications will need to supply an custom PermissionSet to the
        /// script's Run method if more permissions are needed (like the 
        /// SecurityPermissionFlag.UnmanagedCode for MDX applications).
        /// </summary>
        /// <returns>The default permission set our scripts may use</returns>
        public static PermissionSet GetDefaultScriptPermissionSet()
        {       
            PermissionSet internalDefScriptPermSet = new PermissionSet(PermissionState.None);

            internalDefScriptPermSet.AddPermission(new SecurityPermission( SecurityPermissionFlag.Execution ));
            internalDefScriptPermSet.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.MemberAccess));            

            return internalDefScriptPermSet;
        }

        
        private ReferencedAssemblyCollection referencedAssemblies = new ReferencedAssemblyCollection();       
        public ReferencedAssemblyCollection ReferencedAssemblies
        {
            get { return referencedAssemblies; }
            set { referencedAssemblies = value; }
        }
        
        private Assembly cachedScriptAssembly = null;
        private bool sourceChanged = false;

        public Assembly Assembly 
        {
            get { return cachedScriptAssembly; }
        }

        public bool SourceChanged
        {
            get { return sourceChanged; }
            set { sourceChanged = value; }
        }
        
        private ScriptTemplate template = ScriptTemplate.Default;
        public ScriptTemplate Template
        {
            get { return template; }
            set { template = value; }
        }

        private string source = string.Empty;

        public string Source
        {
            get { return source; }
            set 
            {
                sourceChanged = source != value;

                if (sourceChanged)
                {
                    if (ScriptSourceChanged != null)
                    {
                        ScriptSourceChanged(value, source);
                    }
                }

                source = value; 
            }
        }

        internal string TemplatedSource
        {
            get
            {
                return this.Template.ConstructScript(this.Source);
            }
        }

        public Script()
        {           
            
        }

        public void Compile( CompilerOutputDelegate cod )
        {
            AddReferencedAssemblies();
            CompilerResults results = ScriptCompiler.DefaultCompiler.Compile(this, cod);
            if (results.Errors.Count > 0)
            {
                string s = "";
                foreach (CompilerError e in results.Errors)
                {
                    s += e.ToString() + "\r\n\r\n";
                    System.Diagnostics.Debug.WriteLine(e.ToString());
                }
                MessageBox.Show("An error occurred compiling this script:\n\n" + s,
                    "Error compiling script", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                cachedScriptAssembly = results.CompiledAssembly;
                sourceChanged = cachedScriptAssembly != null;
            }
        }

        public void Run(CompilerOutputDelegate cod, object subject, object[] parameters)
        {
            this.Run(cod, subject, parameters, GetDefaultScriptPermissionSet());
        }

        public void Run(CompilerOutputDelegate cod, object subject, object[] parameters, PermissionSet permissionSet)
        {
            if (cachedScriptAssembly == null || sourceChanged)
            {
                // compile if necessary
                cachedScriptAssembly = ScriptCompiler.DefaultCompiler.Compile(this, cod).CompiledAssembly;
                sourceChanged = cachedScriptAssembly != null;
            }


            if (cachedScriptAssembly != null)
            {
                // restrict code security
                permissionSet.PermitOnly();

                // run script
                this.cachedScriptAssembly.GetType( Template.ScriptClassName ).GetMethod( Template.ScriptMainMethodName ).Invoke(subject, parameters);

                // revert security restrictions
                CodeAccessPermission.RevertPermitOnly();
            }
        }

        private void AddReferencedAssemblies()
        {
            ReferencedAssemblyCollection assemblies = ReferencedAssemblies;

            assemblies.Add("System.dll");
            assemblies.Add("System.Windows.Forms.dll");
            string dpath = System.Environment.CurrentDirectory;
            string[] files = System.IO.Directory.GetFiles(dpath);
            foreach (string file in files)
            {
                if (file.EndsWith("dll"))
                {
                    if (!assemblies.Contains(file))
                    {
                        assemblies.Add(file);
                    }
                }
            }
        }

    }
}
